package com.lagou.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * description TPMonitorFilter
 * author wy
 * date 2021-08-10 23:09
 **/
@Activate(group = {CommonConstants.CONSUMER})
public class TPMonitorFilter implements Filter, Runnable
{
    //存储方法执行得相关信息（方法名称，方法执行耗时，方法执行结束时间）
    Map<String, List<MethodInfo>> methodTimeMap = new ConcurrentHashMap<>();

    public TPMonitorFilter()
    {
        // 创建定时线程，每隔5s打印一次最近1分钟内每个方法的TP90、TP99情况
        Executors.newSingleThreadScheduledExecutor()
                .scheduleWithFixedDelay(this, 5, 5, TimeUnit.SECONDS);
    }

    /**
     * 利用TPMonitorFilter在消费端记录每个方法的请求耗时时间
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException
    {
        // 获取执行方法名
        Result result = null;
        long takeTime = 0;
        try
        {
            //起始时间
            long startTime = System.currentTimeMillis();
            result = invoker.invoke(invocation);
            //计算方法执行的耗时
            takeTime = System.currentTimeMillis() - startTime;

            String methodName = invocation.getMethodName();
            List<MethodInfo> methodInfos = methodTimeMap.get(methodName);
            //第一次方法执行
            if (methodInfos == null)
            {
                methodInfos = new ArrayList<>();
            }
            //将当前的方法添加到map中指定的记录当前方法执行情况的集合中
            methodInfos.add(new MethodInfo(invocation.getMethodName(), takeTime, System.currentTimeMillis()));
            methodTimeMap.put(methodName, methodInfos);

            return result;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return result;
        }
        finally
        {
            System.out.println("method = [" + invocation.getMethodName()
                    + "]请求耗时:" + takeTime + "毫秒");
        }
    }

    @Override
    public void run()
    {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = sdf.format(date);
        // 打印线程使用情况
        for (Map.Entry<String, List<MethodInfo>> methodInfos : methodTimeMap.entrySet())
        {
            System.out.println(dateStr + methodInfos.getKey() + "的[TP90]:"
                    + getTP(methodInfos.getValue(), 0.9) + "毫秒," + "[TP99]:"
                    + getTP(methodInfos.getValue(), 0.99) + "毫秒");
        }
    }

    /**
     * 后续会每隔5s调用一次此方法，打印最近1分钟内每个方法的TP90、TP99的耗时情况
     */
    private long getTP(List<MethodInfo> methodInfos, double rate)
    {
        // 构建一个临时集合保存,用于满足1一分钟之内的数据
        List<MethodInfo> sortInfo = new ArrayList<>();
        // 计算最近一分钟的TP90 和 TP99
        long startTime = System.currentTimeMillis() - 60*1000;
        long endTime = System.currentTimeMillis();

        // 遍历列表集合
        int length = methodInfos.size();
        for (int i = 0; i < length; i++)
        {
            MethodInfo methodInfo = methodInfos.get(i);
            if (methodInfo.getEndTimes() >= startTime && methodInfo.getEndTimes() <= endTime)
            {
                //将满足条件的方法信息存储到临时集合中
                sortInfo.add(methodInfo);
            }
        }

        //对满足1一分钟之内的数据进行排序
        sortInfo.sort(new Comparator<MethodInfo>()
        {
            @Override
            public int compare(MethodInfo o1, MethodInfo o2)
            {
                return (int)(o1.getTimes() - o2.getTimes());
            }
        });

        //获取当前排序后集合中的指定百分比数值的位置取整，此位置存储的数据就是当前计算的tp90/99
        int index = (int) (sortInfo.size() * rate);

        return sortInfo.get(index).getTimes();
    }

    static class MethodInfo
    {
        // 方法名
        private String name;
        // 执行耗时
        private long times;
        // 结束时间
        private long endTimes;

        public MethodInfo(String name, long times, long endTimes)
        {
            this.name = name;
            this.times = times;
            this.endTimes = endTimes;
        }

        public String getName()
        {
            return name;
        }

        public void setName(String name)
        {
            this.name = name;
        }

        public long getTimes()
        {
            return times;
        }

        public void setTimes(long times)
        {
            this.times = times;
        }

        public long getEndTimes()
        {
            return endTimes;
        }

        public void setEndTimes(long endTimes)
        {
            this.endTimes = endTimes;
        }
    }
}
